package com.sali.commons.aop.limiter;

import com.google.common.collect.Lists;
import com.sali.commons.annotation.AccessIpLimiter;
import com.sali.utils.exception.BusinessException;
import com.sali.utils.methods.IpUtils;
import com.sali.utils.result.ResultEnum;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;



@Slf4j
@Aspect
@Component
public class AccessIpLimiterAspect{

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    @Qualifier("ipLimiterLuaScript")
    private DefaultRedisScript<Boolean> defaultRedisScript;

    // 定义aop的切入点
    @Pointcut("@annotation(com.sali.commons.annotation.AccessIpLimiter)")
    public void cut() {
        log.info("cut");
    }

    // 拦截属于放入执行之前的行为，所以定义@Before通知
    @Before("cut()")
    public void before(JoinPoint joinPoint) throws IOException {

        // 1：获取方法的签名作为key
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        AccessIpLimiter annotation = method.getAnnotation(AccessIpLimiter.class);
        //2：如果没有增加accessLimiter注解说明不需要限流，直接返回
        if (annotation == null) {
            return;
        }
        // 获取限流缓存的key
        String key = annotation.key();
        // 获取限流缓存的限制次数
        int limit = annotation.limit();
        // 获取限流缓存的时间
        int timeout = annotation.timeout();
        // 获取服务请求的对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        // 获取用户请求地址
        String ipAddress = IpUtils.getIp(request);
        log.info("切面客户端请求的IP地址是：{}", ipAddress);

        // 如果没有设置key，从调用方法的签名自动生成一个key
        if (StringUtils.isEmpty(key)) {
            // 获取方法所有的参数
            String name = method.getDeclaringClass().getName();
            // 获取方法的签名
            key = name + "#" + method.getName() + "#" + ipAddress;
        }
        // 请求lua脚本
        Boolean acquired = stringRedisTemplate.execute(defaultRedisScript,
                Lists.newArrayList(key), Integer.toString(limit), Integer.toString(timeout));
        // 如果超过限流限制
        if (Boolean.FALSE.equals(acquired)) {
            throw new BusinessException(ResultEnum.LIMITE_SERVER_ERROR);
        }
    }
}
